package cn.seaboot.admin.api.mapping;

import cn.seaboot.admin.api.bean.ApiMapping;
import cn.seaboot.admin.api.bean.ApiParameter;
import cn.seaboot.commons.core.CommonUtils;
import cn.seaboot.commons.core.Snowflake;
import cn.seaboot.commons.core.SnowflakeFactory;
import cn.seaboot.commons.reflect.ObjectType;
import cn.seaboot.openapi.base.ControllerInfo;
import cn.seaboot.openapi.base.MethodInfo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.stereotype.Component;

import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 参数扫描
 *
 * @author Mr.css
 * @version 2022-04-19 19:24
 */
@Component
public class ParameterScanner {
    private final Snowflake snowflake = SnowflakeFactory.createSnowflake();

    /**
     * 获取请求参数配置
     *
     * @param mapping      工程ID
     * @param controllerInfo Controller初步解析信息
     * @param methodInfo     方法信息
     * @return 参数信息
     */
    public List<ApiParameter> scanMethodParameter(ApiMapping mapping, ControllerInfo controllerInfo, MethodInfo methodInfo) {
        List<ApiParameter> ret = new ArrayList<>();

        Map<String, Parameter> parameters = methodInfo.getParameters();
        for (Parameter param : parameters.values()) {

            ApiParameter parameter = new ApiParameter();
            parameter.setId(this.snowflake.nextId());
            parameter.setMappingId(mapping.getId());
            parameter.setParameterName(param.getName());

            // src
            parameter.setControllerClass(controllerInfo.getTarget().getName());
            parameter.setMethodName(methodInfo.getMethod().getName());

            // class / name
            Class<?> type = param.getType();
            ObjectType objectType = ObjectType.of(type);
            parameter.setClassName(type.getName());
            parameter.setObjectType(objectType.name());
            parameter.setIsModel(ObjectType.OBJECT.equals(objectType));

            parameter.setProjectId(mapping.getProjectId());

            // try to get parameter descriptions from value-object
            if (parameter.getIsModel()) {
                Schema schema = type.getAnnotation(Schema.class);
                if (schema != null) {
                    parameter.setParameterLabel(schema.description());
                }
            }

            ret.add(parameter);
        }

        // get Parameter from annotation
        this.scanMethodAnnotation(ret, methodInfo);
        return ret;
    }

    /**
     * 从方法的注解上获取参数配置信息
     *
     * @param apiParameters 已经扫描到的参数集合
     * @param methodInfo    方法信息
     */
    private void scanMethodAnnotation(List<ApiParameter> apiParameters, MethodInfo methodInfo) {
        io.swagger.v3.oas.annotations.Parameter[] parameters = null;

        // get parameter from @Operation
        Operation operation = methodInfo.getMethod().getAnnotation(Operation.class);
        if (operation != null) {
            parameters = operation.parameters();
        }

        // get parameter from @Parameters
        if (CommonUtils.isEmpty(parameters)) {
            Parameters params = methodInfo.getMethod().getAnnotation(Parameters.class);
            if (params != null) {
                parameters = params.value();
            }
        }

        // is parameter exist
        if (CommonUtils.isNotEmpty(parameters)) {
            Iterator<ApiParameter> ite = apiParameters.iterator();
            while (ite.hasNext()) {
                ApiParameter apiParameter = ite.next();
                for (io.swagger.v3.oas.annotations.Parameter parameter : parameters) {
                    if (apiParameter.getParameterName().equals(parameter.name())) {
                        if (parameter.hidden()) {
                            // hidden parameter
                            ite.remove();
                        } else {
                            // get description
                            apiParameter.setExample(parameter.example());
                            apiParameter.setParameterLabel(parameter.description());
                        }
                    }
                }
            }
        }
    }
}
